package edu.kit.aifb.ldstream.learner.tasks;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;


import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.rio.ParserConfig;
import org.openrdf.rio.RDFParser.DatatypeHandling;

import edu.kit.aifb.ldstream.env.Environment;
import edu.kit.aifb.ldstream.learner.LiteralChecker;
import edu.kit.aifb.ldstream.learner.dao.Instance;
import edu.kit.aifb.ldstream.learner.dao.ValueDistributionResult;
import edu.kit.aifb.ldstream.learner.dao.Type;
import edu.kit.aifb.ldstream.stat.util.IDistributionChecker;

public class ValueDistributionTask extends LearnerTask {

	private Map<Type, ArrayList<Instance>> classMap;
	private Map<Type, HashMap<URI, ValueDistributionResult>> resultSet;
	private IDistributionChecker distribChecker;

	private int actualNumProbs = 0;
	private int actualNumLinkedProbs = 0;

	public ValueDistributionTask(Map<Type, ArrayList<Instance>> classMap, 
			Map<Type, HashMap<URI, ValueDistributionResult>> resultSet, 
			IDistributionChecker distributionChecker) {
		this.classMap = classMap;
		this.resultSet = resultSet;
		this.distribChecker = distributionChecker;
	}
	@Override
	public void run() {
		if(Environment.VERBOSE){
			System.out.println("Started to learn the value distributions.");
		}
		startup();
		setPreserveBNodes();
		collectValuesAndDistribution();
		shutdown();		

		if(Environment.VERBOSE){
			System.out.println("Value distributions learned.");
		}
	}

	private void collectValuesAndDistribution(){

		Statement s;
		Value obj;
		Literal lit;

		int j = 1;
		double percentage = 0;
		int tenth = 0;

		if(Environment.LOGGING){
			tenth = Math.max(classMap.size() / 10, 1);
			percentage = 100d / classMap.size();
		}

		for(Type type : this.classMap.keySet()){

			if(Environment.LOGGING){
				if((j % tenth) == 0 ){
					Environment.log.info("ValueDistribution leaned to: " + Math.round(j * percentage) +"%");
				}
				j++;
			}
			// only crawl if the class has some instances
			if(!this.classMap.get(type).isEmpty()){
				this.resultSet.put(type, new HashMap<URI, ValueDistributionResult>());
				Map<URI, LinkedList<Double>> valueList = new HashMap<URI, LinkedList<Double>>();
				Map<URI, LinkedList<String>> stringList = new LinkedHashMap<URI, LinkedList<String>>();
				URI objectURI;
				for(Instance instance : this.classMap.get(type)){
					RepositoryResult<Statement> result = null;
					try {
						result = con.getStatements(instance.getURI(), null, null, false);
						while(result.hasNext()){
							this.actualNumProbs++;
							s = result.next();
							obj = s.getObject();
							if(obj instanceof BNode){
								// crawl
								collectValuesAndDistribution((BNode) obj, type, valueList, stringList);
							}else if (obj instanceof Literal){
								lit = (Literal) obj;
								if(LiteralChecker.isContinuousLiteral(lit.getDatatype())){
									// adding if the predicate is not crawled yet we need a new list to save the values
									if(!valueList.containsKey(s.getPredicate())){
										valueList.put(s.getPredicate(), new LinkedList<Double>());
									}
									valueList.get(s.getPredicate()).add(lit.doubleValue());
									// if its qualitative we save the values as strings 
								}else if(LiteralChecker.isDiscreteLiteral(lit.getDatatype())){
									if(!stringList.containsKey(s.getPredicate())){
										stringList.put(s.getPredicate(), new LinkedList<String>());
									}
									stringList.get(s.getPredicate()).add(lit.stringValue());
								}
							}else{
								// no literal
								// check if the linked object is in another domain
								// note cannot be a bnode because of the upper if clause
								objectURI = new URIImpl(s.getObject().stringValue());
								if(type.getURI().getNamespace().equals(objectURI.getNamespace())){
									this.actualNumLinkedProbs++;
								}
								if(!stringList.containsKey(s.getPredicate())){
									stringList.put(s.getPredicate(), new LinkedList<String>());
								}
								stringList.get(s.getPredicate()).add(obj.stringValue());
							}
						}
					} catch (RepositoryException e) {
						e.printStackTrace();
					}
				}
				// all instance of that class are crawled no find out the distribution of the values
				checkContinuousDistribution(type, valueList);
				checkDiscreteDistribution(type, stringList);
				if(this.actualNumProbs != 0){
					type.setLinkageDegree(this.actualNumLinkedProbs / this.actualNumProbs);
					this.actualNumProbs = 0;
					this.actualNumLinkedProbs = 0;
				}
			}
		}
	}

	private void collectValuesAndDistribution(BNode b, 
			Type type,
			Map<URI, LinkedList<Double>> valueList,
			Map<URI, LinkedList<String>> stringList){
		try {
			RepositoryResult<Statement> result = con.getStatements(b, null, null, false);
			Statement s;
			Literal lit;
			Value obj;
			URI objectURI;

			try{
				while(result.hasNext()){
					this.actualNumProbs++;
					s = result.next();
					obj = s.getObject();
					if(obj instanceof BNode){
						collectValuesAndDistribution((BNode) obj, type, valueList, stringList);
					}else if(obj instanceof Literal){
						lit = (Literal) obj;

						if(LiteralChecker.isContinuousLiteral(lit.getDatatype())){
							// adding if the predicate is not crawled yet we need a new list to save the values
							if(!valueList.containsKey(s.getPredicate())){
								valueList.put(s.getPredicate(), new LinkedList<Double>());
							}
							valueList.get(s.getPredicate()).add(lit.doubleValue());
							// if its qualitative we save the values as strings 
						}else if(LiteralChecker.isDiscreteLiteral(lit.getDatatype())){
							if(!stringList.containsKey(s.getPredicate())){
								stringList.put(s.getPredicate(), new LinkedList<String>());
							}
							stringList.get(s.getPredicate()).add(lit.stringValue());
						}
					}else{
						objectURI = new URIImpl(s.getObject().stringValue());
						if(type.getURI().getNamespace().equals(objectURI.getNamespace())){
							this.actualNumLinkedProbs++;
						}
						if(!stringList.containsKey(s.getPredicate())){
							stringList.put(s.getPredicate(), new LinkedList<String>());
						}
						stringList.get(s.getPredicate()).add(obj.stringValue());
					}
				}
			}finally{
				result.close();
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
	}

	private void checkContinuousDistribution(Type t, Map<URI, LinkedList<Double>> valueList){

		for(URI pred : valueList.keySet()){
			// get all the values of the specified predicate
			double[] values = new double[valueList.get(pred).size()];
			int i = 0;
			for(double entry : valueList.get(pred)){
				values[i++] = entry;
			}
			ValueDistributionResult r = new ValueDistributionResult(t.getURI(), pred, this.distribChecker.continuous(values));
			this.resultSet.get(t).put(pred, r);
		}

	}

	private void checkDiscreteDistribution(Type t, Map<URI, LinkedList<String>> stringList){
		ArrayList<String> valueMapping;
		for(URI pred : stringList.keySet()){
			valueMapping = new ArrayList<String>();
			String[] values = new String[stringList.get(pred).size()];
			int i = 0;
			for(String entry : stringList.get(pred)){
				values[i++] = entry;
				if(!valueMapping.contains(entry)){
					// adding the new value to the end of the list
					valueMapping.add(valueMapping.size(), entry);
				}
			}
			String[] mappingEntries = new String[valueMapping.size()];
			ValueDistributionResult r = new ValueDistributionResult(t.getURI(), pred, 
					this.distribChecker.discrete(values, true), 
					valueMapping.toArray(mappingEntries));
			this.resultSet.get(t).put(pred, r);
		}
	}


	private void setPreserveBNodes(){
		this.con.setParserConfig(new ParserConfig(true, true, true, DatatypeHandling.VERIFY));
	}
}




